package ipamdriver


import (
	"net"
	"fmt"
	"strings"
	"strconv"
	"errors"
	// "encoding/json"

	"db"
	"github.com/satori/go.uuid"
)

var (
	MaxNet  = 256
	Two     = "2"
	Prefix  = "24"
)


type SubPool struct {
	Network  string
	Gateway  string
	Type     string
	Vni      string
	Current  string
	Recovery Queue
}

type Pool struct {
	Network	 string
	Current  string
	Recovery Queue
	subnet   map[string]SubPool
}


type FlatIp struct {
	StartIp  string 
	EndIp 	 string
	Gateway  string
}


type FlatPool struct {
	Network   FlatIp
	Current   string
}

func (P *Pool) WriteEtcd(key string) error {
	if err := db.SetKey(key, P); err != nil {
		return err
	}
	return nil
}

func (P *Pool) ReadEtcd(key string) (*Pool, error) {
	value, err := db.GetKey(key, &Pool{})
	if err != nil {
		return &Pool{}, err
	}
	pool, ok := value.(*Pool)
	if !ok {
		return &Pool{}, err
	}
	return pool, nil
}

func (P *Pool) GetGateIp() string {
	value := strings.Split(P.Network, ".")
	return value[0] +"."+ value[1] +"."+ value[2] + ".1/24"
}

func (P *Pool) GetNextNetork() (string, error) {
	var currIp string = P.Current
	temp, _ := strconv.Atoi(P.Current)
	if temp >= MaxNet {
		return "", errors.New("Network pool exhausted.")
	}
	P.Current = strconv.Itoa(temp +  1)
	value := strings.Split(P.Network, ".")
	return value[0] + "." + value[1] + "." + currIp + ".1/24", nil
}

func (P *Pool) CurrentIncOne() error {
	temp, _:= strconv.Atoi(P.Current)
	if temp >= MaxNet {
		return errors.New("Network pool exhausted.")
	}
	P.Current = strconv.Itoa(temp +  1)
	return nil
}

func (P *Pool) RecoveryIsNil() bool {
	return len(P.Recovery.Items) == 0
}

func (S *SubPool) WriteEtcd(key string) error {
	if err := db.SetKey(key, S); err != nil {
		return err
	}
	return nil
}

func (S *SubPool) ReadEtcd(key string) (*SubPool, error) {
	value, err := db.GetKey(key, &SubPool{}); 
	if err != nil {
		return &SubPool{}, err
	}
	subPool, ok := value.(*SubPool)
	if !ok {
		return &SubPool{}, err
	}
	return subPool, nil
}


func (S *SubPool) CurrentIncOne() error {
	temp, _:= strconv.Atoi(S.Current)
	if temp >= MaxNet {
		return errors.New("Network pool exhausted.")
	}
	S.Current = strconv.Itoa(temp +  1)
	return nil
}

type SFPool struct {
	Network     string
	Type        string
	Gateway 	string
}

func (F *SFPool) WriteEtcd(key string) error {
	if err := db.SetKey(key, F); err != nil {
		return err
	}
	return nil
}

func (F *SFPool) DeleteEtcd(key string) error {
	if err := db.DelKey(key); err != nil {
		return err
	}
	return nil
}


func GetMask(ip_cidr string) int {
	_, cidr, _ := net.ParseCIDR(ip_cidr)
	mask, _ := cidr.Mask.Size()
	return mask
}


func Uuid() string {
	u := uuid.NewV4()
	return u.String()
}

func GetNextNetPool(poolType, poolID, vni, current string) (string, error) {
	var nextNetwork string
	var err error
	pool := new(Pool)
	key := fmt.Sprintf("%s/%s",poolType, "info")
	pool, _  = pool.ReadEtcd(key)
	// Check whether the Recovery parameter is empty?
	// Get the Network value from Recovery first
	if !pool.Recovery.IsEmpty() {
		nextNetwork = pool.Recovery.Pop()
		if err = pool.WriteEtcd(key); err != nil {
			return "", err
		}
		subPool := SubPool{
			Network:  nextNetwork,
			Gateway:  getGatewayIP(nextNetwork),
			Type:     poolType,
			Vni:      vni,
			Current:  current,
			Recovery:  Queue{
				Items: make([]string, 0, 1),
			},
		}
		if err = subPool.WriteEtcd(fmt.Sprintf("subnet/%s", poolID)); err != nil {
			return "", err
		}
		return nextNetwork, nil
	}
	//Get the Network value from Pool second.
	if nextNetwork, err = pool.GetNextNetork(); err != nil {
		return "", err
	}
	// current paratemer value inc one
	// pool.CurrentIncOne()
	subPool := SubPool{
		Network:  nextNetwork,
		Gateway:  getGatewayIP(nextNetwork),
		Type:     poolType,
		Vni:      vni,
		Current:  current,
		Recovery:  Queue{
			Items: make([]string, 0, 1),
		},
	}
	// update Pool & SubPool value.
	pool.WriteEtcd(key)
	subPool.WriteEtcd(fmt.Sprintf("subnet/%s", poolID))
	return nextNetwork, nil
}


func SetNetworkInfo(network, gateway, poolID, poolType, vni, current string) error {
	subPool := SubPool{
		Network:  network,
		Gateway:  gateway,
		Type:     poolType,
		Vni:      vni,
		Current:   current,
		Recovery:  Queue{
			Items: make([]string, 0, 1),
		},
	}
	if err := subPool.WriteEtcd(fmt.Sprintf("subnet/%s",poolID)); err !=nil{
		return err
	}
	return nil
}

func GetNextAddress(poolID  string) (string,error){
	key := fmt.Sprintf("subnet/%s",poolID)
	subPool := &SubPool{}
	subPool,_ = subPool.ReadEtcd(key)
	network := strings.Split(subPool.Network, "/")
	if !subPool.Recovery.IsEmpty() {
		nextNetwork := subPool.Recovery.Pop()
		if err := subPool.WriteEtcd(key); err != nil {
			return "", err
		}
		return nextNetwork+"/"+ network[1],nil
	}else{
		prefix := strings.Split(network[0], ".")
		result:= prefix[0] + "." + prefix[1] + "." + prefix[2] + "."+ subPool.Current +"/"+ network[1]
		subPool.CurrentIncOne()
		subPool.WriteEtcd(key)
		return result, nil
	}
}


func SetFlatNet(poolID, network, poolType string) error {
	sfPool := SFPool{
		Network:  network,
		Type:     poolType,
		// Gateway:  getPoolAttr(poolType, "Gateway"),
	}
	if err := sfPool.WriteEtcd(fmt.Sprintf("subnet/%s",poolID)); err != nil {
		return err
	}
	return nil
}


// According to network value get the gateway,default gateway: xx.xx.xx.1
// network:  192.168.159.1.0/24   or 192.168.159.0 
func getGatewayIP(network string) string {
	value := strings.Split(network, ".")
	return value[0] +"."+ value[1] +"."+ value[2] + ".1/" + Prefix
}


func CurrentIsOne(poolID string) (bool, error) {
	subPool := &SubPool{}
	subPool, _ = subPool.ReadEtcd(fmt.Sprintf("subnet/%s",poolID))
	if subPool.Current == Two {
		return true, nil
	}
	return false, nil
}


func deleteNetwork(poolID string) error {
	err := db.DelKey(fmt.Sprintf("subnet/%s",poolID))
	return err
}

func updateRecovery(networkType, network string) error {
	key := fmt.Sprintf("%s/%s",networkType, "info")
	pool := &Pool{}
	pool,_ = pool.ReadEtcd(key)
	// info.Recovery = append(info.Recovery, network)
	pool.Recovery.Push(network)
	pool.WriteEtcd(key)
	return nil
}

func updateCurrent(poolID string) error {
	key := fmt.Sprintf("subnet/%s",poolID)
	subPool := &SubPool{}
	subPool,_ = subPool.ReadEtcd(key)
	subPool.CurrentIncOne()
	subPool.WriteEtcd(key)
	return nil
}


func getNetAttr(poolID, attr string) string {
	var result string
	subPool := &SubPool{}
	subPool,_ = subPool.ReadEtcd(fmt.Sprintf("subnet/%s",poolID))
	switch attr{
	case "Type":
		result = subPool.Type
	case "Network":
		result = subPool.Network
	case "Prefix":
		result = strings.Split(subPool.Network,"/")[1]
	}
	return result
}


func getPoolAttr(poolType, attr string) string {
	var result string
	// value,_ := db.GetKey(key)
	pool := &Pool{}
	pool, _ = pool.ReadEtcd(fmt.Sprintf("%s/info",poolType))
	switch attr{
	case "Network":
		result = pool.Network
	}
	return result
}

func networkInPool(poolID, networkType string) bool{
	network := strings.Split(getNetAttr(poolID, "Network"), ".")
	pool := strings.Split(getPoolAttr(networkType, "Network"), ".")
	return network[0] == pool[0] && network[1] == pool[1]
}

/*
//https://blog.csdn.net/lucky404/article/details/90738762
def getRecovery(recovery []string) string {
	
}

*/

func releaseAddress(poolId, address string) error {
	subPool := &SubPool{}
	subPool,_ = subPool.ReadEtcd(fmt.Sprintf("subnet/%s",poolId))
	subPool.Recovery.Push(address)
	fmt.Printf("%v\n",subPool)
	if err := subPool.WriteEtcd(fmt.Sprintf("subnet/%s",poolId)); err != nil {
		return err
	}
	return nil 
}